package ru.susu.algebra.ranks;

import java.lang.reflect.Array;
import java.math.BigInteger;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.BitSet;
import java.util.Date;
import java.util.Map;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

import com.google.common.collect.Maps;

import ru.susu.algebra.number.MapPrimeNumbersProvider;
import ru.susu.algebra.util.LRUHashMap;

/**
 * Вычисление рангов на основе разложения на множители и xorа для определения квадратов. Информация хранится в БД
 *
 * @author akargapolov
 * @since: 10.03.2011
 */
public class FullDPRanksCounterPostgreSQL
{
	private static Logger _log = Logger.getLogger("ranks");

	public static int NUMBER = 200;

	private static Boolean[][][] _counted = (Boolean[][][]) Array.newInstance(Boolean.class, NUMBER + 3, NUMBER + 3, 4);

	private static Map<Integer, BitSet> _factors = new NumbersBitSetFactorizationProvider().performOperation(NUMBER);

	private static Map<Integer, Integer> _primes = new MapPrimeNumbersProvider().performOperation(NUMBER);

	private static Connection _connection;

	private static LRUHashMap<Object, Object> _cache = new LRUHashMap<Object, Object>(1000000);

	private static long _fakeCacheKey = 0;

	public static void main(String[] args) throws Exception
	{
		BasicConfigurator.configure();

		for (int i = 1; i <= NUMBER; i++)
		{
			Date start = new Date();
			_connection = getConnection();
			BigInteger rank = BigInteger.ZERO;
			for (int j = 1; j <= i; j += 2)
			{
				Map<BitSet, BigInteger> map = recCounter(i, j, 0);

				for (Map.Entry<BitSet, BigInteger> entry : map.entrySet())
					if (!entry.getKey().isEmpty())
					{
						// System.out.println(entry.getKey().toString());
						rank = rank.add(entry.getValue());
					}
			}

			if (i % 10 == 0)
				System.gc();
			_connection.commit();
			_connection.close();
			_log.info("cache size - " + _cache.size());
			_log.info("rank[" + i + "] - " + rank);
			_log.info("total - " + Runtime.getRuntime().totalMemory() / 1000000);
			_log.info("free - " + Runtime.getRuntime().freeMemory()/ 1000000);
			_log.info("used - " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/ 1000000);
			_log.info("time - " + (new Date().getTime() - start.getTime()) + "ms");
		}
		_log.info("finished");
		Thread.sleep(120000);
	}

	protected static Map<BitSet, BigInteger> recCounter(int i, int k, int shift) throws Exception
	{
		Map<BitSet, BigInteger> data = getData(i, k, shift);
		if (!data.isEmpty() || _counted[i][k][shift] != null)
		{
			return data;
		}
		if (i == 0 && shift == 0)
		{
			data.put(new BitSet(_primes.size()), BigInteger.ONE);
		}
		else if (k < i)
		{
			for (int j = k + 2; j <= i; j += 2)
				append(data, recCounter(i - k, j, (shift + 1 - k + 4 * i) % 4), k);
		}
		else if (k == i)
		{
			append(data, recCounter(i - k, k, (shift + 1 - k + 4 * i) % 4), k);
		}
		saveData(i, k, shift, data);
		_counted[i][k][shift] = true;
		return data;
	}

	protected static void append(Map<BitSet, BigInteger> map1, Map<BitSet, BigInteger> map2, Integer add)
	{
		for (Map.Entry<BitSet, BigInteger> entry : map2.entrySet())
		{
			BitSet bitset = entry.getKey();
			if (add != null)
			{
				bitset = (BitSet) bitset.clone();
				bitset.xor(_factors.get(add));
			}
			BigInteger value = map1.get(bitset);
			if (value == null)
			{
				value = entry.getValue();
			}
			else
			{
				value = value.add(entry.getValue());
			}
			map1.put(bitset, value);
		}
	}

	private static Connection getConnection() throws SQLException
	{
		try
		{
			Class.forName("org.postgresql.Driver");
			Connection connection = DriverManager.getConnection("jdbc:postgresql://localhost:5432/alternating_ranks",
					"postgres", "1234");
			connection.setAutoCommit(true);
			return connection;
		}
		catch (ClassNotFoundException e)
		{
			throw new SQLException(e);
		}
	}

	protected static Map<BitSet, BigInteger> getData(int i, int k, int shift) throws Exception
	{
		String key = i + "_" + k + "_" + shift;
		if (_cache.containsKey(key))
			return (Map<BitSet, BigInteger>)_cache.get(key);

		Map<BitSet, BigInteger> result = Maps.newHashMap();
		_cache.put(key, result);
		PreparedStatement psmt = _connection.prepareStatement("SELECT bitset, bvalue FROM tbl_data where index = ?");
		psmt.setInt(1, getIndex(i, k, shift));
		ResultSet rs = psmt.executeQuery();
		while (rs.next())
		{
			BitSet bitset = parseBitSet(rs.getString(1));
			BigInteger value = new BigInteger(rs.getString(2));
			result.put(bitset, value);
			_cache.put(_fakeCacheKey++, value);
		}
		rs.close();
		psmt.close();
		return result;
	}

	protected static int getIndex(int i, int k, int shift)
	{
		return i * 10000 * 10 + k * 10 + shift;
	}

	protected static void saveData(int i, int k, int shift, Map<BitSet, BigInteger> data) throws Exception
	{
		if (data.isEmpty())
		{
			return;
		}
		PreparedStatement psmt = _connection.prepareStatement("insert into tbl_data(index,bitset,bvalue) values (?,?,?)");
		for (Map.Entry<BitSet, BigInteger> entry : data.entrySet())
		{
			psmt.setInt(1, getIndex(i, k, shift));
			psmt.setString(2, entry.getKey().toString());
			psmt.setString(3, entry.getValue().toString());
			psmt.addBatch();
		}
		psmt.executeBatch();
		psmt.close();
	}

	protected static BitSet parseBitSet(String value)
	{
		BitSet bitset = new BitSet(_primes.size());
		for (String intValue : value.substring(1, value.length() - 1).split(","))
		{
			String trimmed = intValue.trim();
			if (!trimmed.isEmpty())
			{
				bitset.set(Integer.valueOf(trimmed));
			}
		}
		return bitset;
	}
}
